/** JXSON - JavaScript XML Object Notation
 * @author  Andrea Giammarchi
 * @blog    webreflection.blogspot.com
 * @license Mit Style License
 * @version 0.2
 * 
 * DEMOS/DOCUMENTATION/BLOG:
 * src  => http://www.devpro.it/code/193.html
 * doc  => http://webreflection.blogspot.com/2008/07/jxon-lossless-javascript-to-xml-object.html
 * demo => http://www.3site.eu/JXON/
 * 
 * 
 * USAGE EXAMPLE:
 *  JXON.load(JXON.toXML({
        name:       "Andrea",
        birthDate:  new Date(264227400460),
        age: 24,
        address : {
            street : "888 Oak Tree Lane",
            zip: 10090
        }
	})).using(myXSLDocument).into(myNode);
	
	.toXML  => transforms a js object/variable into a valid JXON XML node
	.using(myXSLDocument) => transforms the xml using xsl
	.into(myNode); => outputs the XHTML into a node in the DOM.

 */
(function(){
JXON = {

    /**
     * Transform a JXON compatible XML node into JXON compatible variable.
     * A valid JXON XML node can be created via JXON itself, or manually, and with every language.
     * Its content should be inside a <jxon> node, and without spaces between each node.
     * JXON XML node structure
     * -----------------------
     *  root
     *      elements
     *
     *  root        <jxon>elements</jxon>
     *
     *  elements    one or more element
     *
     *  element     <array(key)?>elements</array>
     *              <boolean(key)?>true|false</boolean>
     *              <date(key)?>YYYY-MM-DDTHH:II:SS</date>
     *              <null(key)?/>
     *              <object(key)?>elements</date>
     *              <string(key)?>unicode</string>
     *
     *  key         ' key="' + unicode + '"'
     *              If a node represents an obejct property,
     *              it has to contain a key attribute, with an unicode value
     *              e.g.
     *              <jxon><object><string key="name">Andrea</string></object></jxon>
     *              is transformed into: {name:"Andrea"}
     *
     *  unicode     a JavaScript / DOM compatible string
     *
     * @param   Mixed   an optional valid JXON XML node or nothig to use internal xml property
     * @return  Mixed   native JavaScript representation of JXON node.
     * @example

        // file 123.xml
        <jxon><array><number>1</number><number>2</number><number>3</number></array></jxon>
        
        // transformation via syncronous load
        // output:1 - 2 - 3
        
        document.write(
            JXON.load(
                "123.xml"
            ).fromXML().join(" - ")
        );
      
        // transformation via JXON itself
        // output:1 - 2 - 3
        document.write(
            JXON.fromXML(
                JXON.toXML([1, 2, 3])
            ).join(" - ")
        );

     */
    fromXML:function(JXON){
        return fromXML((JXON || this.xml).firstChild)
    },

    /**
     * Transform a JXON compatible JavaScript variable into a valid JXON XML node.
     * The only reason to convert a variable into XML is to apply an XSL stylesheet.
     * This means that you should not append a valid JXON XML node directly in your page,
     * specially because some browser (IE) cannot append an XML node into an (x)HTML DOM.
     * A valid JXON variable is an array, a boolean, a date, a null, a number, an object, or a string.
     * Please note that since JXON is based on JavaScript Object Notation, it is not able (yet?) to understand
     * native instances. e.g. wrongJXONvar = {name:new String("Andrea")}; goodJXON = {name:"Andrea"};
     * The only inevitable exception is a Date instance, so this variable will be correctly parsed.
     *
     * @param   Mixed   A valid JXON JavaScript variable
     * @return  XML     A JXON compatible XML node
     * @example

        var myXMLNode = JXON.toXML({
            name:       "Andrea",
            birthDate:  new Date(264227400460)
        });
 
        // <jxon><object><string key="name">Andrea</string><date key="birthDate">1978-05-17T05:30:00</date></object></jxon>

     */
    toXML:function(JXON){
        // method re-defined later for cross browser compatibility
    },

    /**
     * Load a valid JXON XML node or a generic XML file (that could or not contain a valid JXON node).
     * If the parameter is already an XML, this method directly assigns them into public xml property.
     *
     * @param   Mixed   An XML node or a string that will be used to load syncronously an external XML file.
     * @return  JXON    JXON object itself
     * @example

        // using the same fromXML method example file, xml variable will be this XML
        <jxon><array><number>1</number><number>2</number><number>3</number></array></jxon>
        
        // load syncronously
        JXON.load("123.xml").xml;

        // direct xml set, will be true
        JXON.load(myOwnXML).xml === myOwnXML;

     */
    load:function(JXON){
        this.xml = typeof JXON == "string" ? load(JXON) : JXON;
        return  this
    },

    /**
     * Load a generic XSL file to use for XML transformation.
     * If the parameter is already an XSL, this method directly assigns them into public xsl property.
     *
     * @param   Mixed   An XSL document or a string that will be used to load syncronously an external XSL file.
     * @return  JXON    JXON object itself
     * @example

        // load syncronously and set JXON.xsl
        JXON.using("123.xsl");

        // direct xsl set, will be true
        JXON.using(myOwnXSL).xsl === myOwnXSL;

        // cascade loads
        JXON.load("123.xml").using("123.xsl");

     */
    using:function(JXON){
        this.xsl = typeof JXON == "string" ? load(JXON) : JXON;
        return  this
    },

    /**
     * Transform public xml property into (x)HTML using public xsl property.
     * Put the transformation result into specified node, removing every child node, if any.
     *
     * @param   DOMNode A generic DOM node to use for transformation result.
     * @return  JXON    JXON object itself
     * @example

        // cascade loads and transformation
        JXON.load("123.xml").using("123.xsl").into(document.body);

        // multiple transformations with a single XSL
        JXON.using("mysite.xsl")
            .load("authors.xml").into(myAuthorsDiv)
            .load("books.xml").into(myBooksList)
            .load("e-sites.xml").into(myECommerceSiteList)
        ;

     */
    into:function(node){
        // method re-defined later for cross browser compatibility
    }
};

/** closure properties and functions **/
function i(i){
    return i < 10 ? "0" + i : i
};
function fromDate(Date){
    return "".concat(Date.getFullYear(), "-", i(Date.getMonth() + 1), "-", i(Date.getDate()), "T", i(Date.getHours()), ":", i(Date.getMinutes()), ":", i(Date.getSeconds()))
};
function toDate(nodeValue){
    var result = new Date;
    with(result){
        setFullYear(nodeValue.substr(0, 4));
        setMonth(nodeValue.substr(5, 2) - 1);
        setDate(nodeValue.substr(8, 2));
        setHours(nodeValue.substr(11, 2));
        setMinutes(nodeValue.substr(14, 2));
        setSeconds(nodeValue.substr(17, 2));
    };
    return result
};
function toXML(JXON){
    var node, type = typeof JXON;
    switch(type){
        case    "boolean":
        case    "number":
        case    "string":
            (node = root.createElement(type)).appendChild(root.createTextNode(JXON));
            break;
        case    "object":
            switch(true){
                case    JXON instanceof Array:
                    for(var i = 0, length = JXON.length, node = root.createElement("array"); i < length; i++)
                        if(type = toXML(JXON[i]))
                            node.appendChild(type);
                    break;
                case    JXON instanceof Date:
                    node = root.createElement("date");
                    node.appendChild(root.createTextNode(fromDate(JXON)));
                    break;
                case    JXON instanceof Object:
                    node    = root.createElement(type);
                    for(var key in JXON)
                        if(type = toXML(JXON[key])){
                            type.setAttribute("key", key);
                            node.appendChild(type);
                        };
                    break;
                case    JXON === null:
                    node    = root.createElement("null");
                    break;
            };
            break;
    };
    return node
};
function fromXML(JXON){
    var firstChild = JXON.firstChild;
    if(firstChild)switch(firstChild.nodeType){
        case    1:
            switch(JXON.nodeName.toLowerCase()){
                case    "array":
                    for(var i = 0, length = JXON.childNodes.length, result = new Array(length); i < length; i++)
                        result[i] = fromXML(JXON.childNodes[i]);
                    return  result;
                case    "object":
                    for(var i = 0, length = JXON.childNodes.length, result = {}, node; i < length; i++)
                        result[(node = JXON.childNodes[i]).getAttribute("key")] = fromXML(node);
                    return result;
            };
        case    3:
            switch(JXON.nodeName.toLowerCase()){
                case    "boolean":  return firstChild.nodeValue === "true";
                case    "date":     return toDate(firstChild.nodeValue);
                case    "number":   return parseFloat(firstChild.nodeValue);
                case    "string":   return firstChild.nodeValue;
            }
    };
    return  null
};
if(this.ActiveXObject){
    var root = new ActiveXObject("Microsoft.XMLDOM"),
        load = function(xml){
            var xhr = new ActiveXObject("Microsoft.XMLDOM");
            xhr.async = false;
            xhr.load(xml);
            return xhr
        };
    JXON.into = function(node){
        node.innerHTML = this.xml.transformNode(this.xsl);
        return  this
    };
    JXON.toXML = function(JXON){
        var node = root.createElement("jxon");
        node.appendChild(toXML(JXON));
        return node
    }
} else {
    var root = document,
        load = function(xml){
            var xhr = new XMLHttpRequest;
            xhr.open("GET", xml, false);
            xhr.send(null);
            return xhr.responseXML
        };
    JXON.into = function(node){
        var xp = new XSLTProcessor;
        xp.importStylesheet(this.xsl);
        while(node.hasChildNodes())
            node.removeChild(node.childNodes[0]);
        node.appendChild(xp.transformToFragment(this.xml, root));
        return  this
    };
    JXON.toXML = function(JXON){
        var node = root.createElement("jxon");
        node.appendChild(toXML(JXON));
        return node
    }
}})();